var fs = require('fs');
var path = require('path');
var compress = require('./compression');
var request = require('./request');
var response = require('./response');

module.exports = function (req, res, sessionManager) {
    if (sysConfig.isDebug) {
        purgeCache();
    }

    exec(req, res, sessionManager);
};

function exec(req, res, sessionManager) {
    compress()(req, res);

    new request(req, res, sessionManager, function () {
        new response(req, res, sessionManager, interceptorAfter);
        interceptorBefore(req, res, function () {
            execAction(req, res, 0);
        });
    });

    process.on('uncaughtException', function (err) {
        req.emit('error', err);
    });

    process.on('unhandledRejection', function (err) {
        req.emit('error', err);
    });
}

function urlMatch(url, pathname) {
    if (typeof url === 'string') {
        return pathname.toLowerCase().startsWith(url.toLowerCase());
    } else {
        return url.test(pathname);
    }
}

function interceptorBefore(req, res, next) {
    if (interceptorBeforeMap.length == 0) {
        return next();
    }

    req.__interceptorBeforeIndex = req.__interceptorBeforeIndex || 0;
    if (req.__interceptorBeforeIndex == interceptorBeforeMap.length || !urlMatch(interceptorBeforeMap[req.__interceptorBeforeIndex].url, req.pathname)) {
        next();
    } else {
        try {
            interceptorBeforeMap[req.__interceptorBeforeIndex].dirOrInvoke(req, res, function (isContinu) {
                if (isContinu !== false) {
                    req.__interceptorBeforeIndex++;
                    interceptorBefore(req, res, next);
                }
            });
        } catch (err) {
            res.send500(err);
        }
    }
}

function interceptorAfter(req, res, chunk, next) {
    req.__interceptorBeforeIndex = interceptorBeforeMap.length;
    if ((res.statusCode && res.statusCode != 200) || typeof chunk != 'string' || interceptorAfterMap.length == 0) {
        return next(chunk);
    }

    req.__interceptorAfterIndex = req.__interceptorAfterIndex || 0;
    if (req.__interceptorAfterIndex == interceptorAfterMap.length || !urlMatch(interceptorAfterMap[req.__interceptorAfterIndex].url, req.pathname)) {
        next(chunk);
    } else {
        interceptorAfterMap[req.__interceptorAfterIndex].dirOrInvoke(req, res, chunk, function (chunk, isContinu) {
            if (isContinu === false) {
                next(chunk || '', true);
            } else {
                req.__interceptorAfterIndex++;
                interceptorAfter(req, res, chunk, next);
            }
        });
    }
}

function execAction(req, res, index) {
    try {
        for (var i = index; i < routeMap.length; i++) {
            if (urlMatch(routeMap[i].url, req.pathname)) {
                if (typeof routeMap[i].dirOrInvoke === 'string' && sysConfig.mime[path.extname(req.pathname.substr(routeMap[i].url.length)).substr(1)]) {
                    res.sendFile(path.join(routeMap[i].dirOrInvoke, req.pathname.substr(routeMap[i].url.length)), true);
                    return;
                } else if (typeof routeMap[i].dirOrInvoke === 'function') {
                    routeMap[i].dirOrInvoke(req, res, function () {
                        execAction(req, res, ++index);
                    });
                    return;
                } else if (typeof routeMap[i].dirOrInvoke === 'object') {
                    var controllerDir = routeMap[i].dirOrInvoke.dir.startsWith('./') ? path.join(process.cwd(), routeMap[i].dirOrInvoke.dir) : routeMap[i].dirOrInvoke.dir;
                    req.controller = routeMap[i].dirOrInvoke.controller;
                    req.action = routeMap[i].dirOrInvoke.action;
                    req.invoke = routeMap[i].dirOrInvoke.invoke;
                    req.routeData = [];
                    var resolveUrls = req.pathname.substr(routeMap[i].url.length).split('/');
                    if (req.controller == null) {
                        req.controller = resolveUrls[0] || 'home';
                        req.action = resolveUrls[1] || 'index';
                        req.invoke = resolveUrls[2] || 'index';
                        req.routeData = resolveUrls.slice(3);
                    } else if (req.action == null) {
                        req.action = resolveUrls[0] || 'index';
                        req.invoke = resolveUrls[1] || 'index';
                        req.routeData = resolveUrls.slice(2);
                    } else if (req.invoke == null) {
                        req.invoke = resolveUrls[0] || 'index';
                        req.routeData = resolveUrls.slice(1);
                    }

                    var _module = path.join(controllerDir, req.controller, req.action + '.js');
                    fs.exists(_module, exists => {
                        if (exists) {
                            var obj = require(_module);
                            var method = req.method.toLowerCase();
                            if (obj[method] && obj[method][req.invoke]) {
                                obj[method][req.invoke](req, res);
                            } else {
                                res.send404();
                            }
                        } else {
                            res.send404();
                        }
                    });
                    return;
                }
            }
        }

        res.send404();
    } catch (err) {
        res.send500(err);
    }
}

function purgeCache() {
    var dir = process.cwd();
    Object.keys(require.cache).forEach(function (cacheKey) {
        if (cacheKey.indexOf(dir) != -1 && cacheKey.indexOf('node_modules') == -1) {
            delete require.cache[cacheKey];
        }
    });

    Object.keys(module.constructor._pathCache).forEach(function (cacheKey) {
        if (cacheKey.indexOf(dir) != -1 && cacheKey.indexOf('node_modules') == -1) {
            delete module.constructor._pathCache[cacheKey];
        }
    });
}